﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;
using System.Collections;

namespace NoMvvm
{
    /// <summary>
    /// Dynamic ViewModel base class
    /// </summary>
    public class ViewModel : DynamicObject, INotifyPropertyChanged
    {
        /// <summary>
        /// Contains "this", use it to access this as type dynamic
        /// </summary>
        protected readonly dynamic This;

        private Dictionary<string, object> _dictionary = new Dictionary<string, object>();

        /// <summary>
        /// Copy constructor, copies all properties to ViewModel
        /// </summary>
        /// <param name="obj">Object to "auto-copy" properties from</param>
        /// <param name="autoReflect">By default, your class is searched for methods matching Action or Action&lt;dynamic&gt; which are converted to ICommand</param>
        public ViewModel(object obj, bool autoReflect = true)
            : this(autoReflect)
        {
            IEnumerable<string> properties;

            if (obj is ViewModel)
            {
                properties = ((ViewModel)obj)._dictionary.Keys;
            }
            else if (obj is IDictionary)
            {
                properties = ((IDictionary)obj).Keys.Cast<object>().Select(o => o.ToString());
            }
            else if (obj is IDictionary<string, object>)
            {
                properties = ((IDictionary<string, object>)obj).Keys;
            }
            else if (obj is DynamicObject)
            {
                properties = ((DynamicObject)obj).GetDynamicMemberNames();
            }
            else
            {
                properties = obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Select(p => p.Name);
            }

            foreach (string propertyName in properties)
            {
                SetMember(propertyName, DynamicUtility.GetDynamicMember(obj, propertyName));
            }
        }

        /// <summary>
        /// Constructor with optional parameter
        /// </summary>
        /// <param name="autoReflect">By default, your class is searched for methods matching Action or Action&lt;dynamic&gt; which are converted to ICommand</param>
        public ViewModel(bool autoReflect = true)
        {
            This = this;
            if (autoReflect)
            {
                ReflectMethods();
            }
        }

        /// <summary>
        /// Provides a reference to the View mapped to this ViewModel
        /// NOTE: calls the MEF container every time, store in a local variables if needed to reference it over and over
        /// PS: don't use this property, except for edges cases (breaks MVVM pattern a bit)
        /// </summary>
        public virtual dynamic View
        {
            get 
            {
                dynamic view = IoC.GetViewFor(this);
                view.DataContext = (dynamic)this;
                return view;
            }
        }

        /// <summary>
        /// Override on DynamicObject
        /// </summary>
        /// <returns></returns>
        public override IEnumerable<string> GetDynamicMemberNames()
        {
            return _dictionary.Keys;
        }

        /// <summary>
        /// Override on DynamicObject
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            SetMember(binder.Name, value);
            return true;
        }

        /// <summary>
        /// Override on DynamicObject
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            if (!TryGetMember(binder.Name, out result))
            {
                result = null;
            }
            return true;
        }

#if SILVERLIGHT

        /// <summary>
        /// Override on DynamicObject for Silverlight
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="indexes"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
        {
            return TryGetMember(indexes.First().ToString(), out result);
        }

        /// <summary>
        /// Override on DynamicObject for Silverlight
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="indexes"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
        {
            SetMember(indexes.First().ToString(), value);
            return true;
        }

        /// <summary>
        /// Indexer to support Silverlight
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public object this[string index]
        {
            get
            {
                object value;
                TryGetMember(index, out value);
                return value;
            }
            set { SetMember(index, value); }
        }

#endif

        internal bool TryGetMember(string name, out object result)
        {
            return _dictionary.TryGetValue(name, out result);
        }

        internal void SetMember(string name, object value)
        {
            if (value is Action<dynamic>)
            {
                _dictionary[name] = new Command(name, this, (Action<dynamic>)value);
            }
            else if (value is Action)
            {
                _dictionary[name] = new Command(name, this, (Action)value);
            }
            else
            {
                _dictionary[name] = value;
            }
#if SILVERLIGHT
            PropertyChanged(this, new PropertyChangedEventArgs(string.Empty));
#endif
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

        private void ReflectMethods()
        {
            var type = GetType();
            if (type != typeof(ViewModel))
            {
                foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance))
                {
                    //Return type has to be void
                    //ViewModel base class won't have any methods to reflect
                    //object has a Finalize() method
                    if (method.ReturnType != typeof(void) ||
                        method.DeclaringType == typeof(ViewModel) ||
                        method.DeclaringType == typeof(object))
                    {
                        continue;
                    }

                    var parameters = method.GetParameters();
                    if (parameters.Length == 0)
                    {
                        SetMember(method.Name, Delegate.CreateDelegate(typeof(Action), this, method.Name));
                    }
                    else if (parameters.Length == 1 && parameters[0].ParameterType == typeof(object))
                    {
                        SetMember(method.Name, Delegate.CreateDelegate(typeof(Action<dynamic>), this, method.Name));
                    }
                }
            }
        }

        /// <summary>
        /// Override to handle exceptions during an ICommand execution
        /// </summary>
        /// <param name="exc"></param>
        public virtual void Rescue(Exception exc)
        { }

        /// <summary>
        /// Implements INotifyPropertyChanged
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged = delegate { };
    }
}
